/**
 * 
 */
package org.vsg.stock.core.indicator.kline;

import java.text.DecimalFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vsg.stock.core.KLineStock;
import org.vsg.stock.core.algorithm.EstimatedLineRegression;
import org.vsg.stock.core.algorithm.ListQuickSort;
import org.vsg.stock.core.algorithm.Sortable;
import org.vsg.stock.core.indicator.coordinate.Coordinate;
import org.vsg.stock.core.indicator.coordinate.CoordinatePoint;
import org.vsg.stock.core.indicator.coordinate.OriginalDataPoint;
import org.vsg.stock.core.indicator.coordinate.origpoint.KLineODPFactory;
import org.vsg.stock.core.indicator.coordinate.shape.LineReliability;
import org.vsg.stock.core.indicator.coordinate.shape.RelativeEstimatedLineEquation;
import org.vsg.stock.core.indicator.coordinate.shape.SimpleRelativeEstimateLineEquation;
import org.vsg.stock.core.indicator.coordinate.shape.TrendingLineReliability;
import org.vsg.stock.core.kline.DailyKLineStock;

/**
 * @author Bill Vison
 *
 */
public class UpwardTrendingLineBuilder extends TrendingLineBuilder {

	private static final Logger logger = LoggerFactory.getLogger(UpwardTrendingLineBuilder.class);
	
	private Coordinate coordinate;
	
	/**
	 * this field using debug for the source
	 */
	private long spendStartTime = 0;
	
	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingLineBuilder#buildUp()
	 */
	@Override
	void buildUp() {

		spendStartTime = System.currentTimeMillis();
			
		// --- find the zig item ---
		List<OriginalDataPoint> keyPoints = findKeyDataPointsInReferedCycle();
		
		if (logger.isDebugEnabled() ) {
			logger.info("The following data points in the cycle ");
			if ( keyPoints.size() == 0) {
				logger.warn("No key point founded! Please check");
			}
		}
		
		
		coordinate = buildUpCoordinate();

		List<DegreesCoordinatePoint>  coorPoints = findCoordinatePointList(keyPoints);

		List<DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint>> basicGroupPoints =  clusterBasicGroupPoints(coorPoints);

		// --- find anotherpoint ----
		List<OriginalDataPoint> commonPoints = findCommonPointsinReferedCycle();

		List<DegreesCoordinatePoint>  commonCoorPoints = findCoordinatePointList(commonPoints);
		System.out.println(2343);



		if ( logger.isInfoEnabled() ) {
			logger.info("3. execute program using time : " + (System.currentTimeMillis() - spendStartTime) + "ms");
		}				
		mergeToBasicGroupPoints(
				basicGroupPoints , 
				commonCoorPoints);
		if ( logger.isInfoEnabled() ) {
			logger.info("4. execute program using time : " + (System.currentTimeMillis() - spendStartTime) + "ms");
		}		
	
	
		List<Double> allCoefficients =  guessSomeLineCoefficient(basicGroupPoints , commonCoorPoints);

	
		
		Map<Double, List<DegreesCoordinatePoint>> sortPointsMap = sortPointsMap(commonCoorPoints);
		
		
		Map<Double ,LineReliability>  valiCoefficients = validateReliability(allCoefficients , sortPointsMap);
		
		// ---- build lines ----
		buildLines(valiCoefficients);
		

	}
	
	
	private Map<Double ,LineReliability> validateReliability(List<Double> allCoefficients , Map<Double, List<DegreesCoordinatePoint>> pointsMap) {
		
		Map<Double ,LineReliability> lineReliabilityMap = new HashMap<Double, LineReliability>();
		
		TrendingLineReliability tr  = null;
		for (Double coefficients : allCoefficients) {
			tr = new TrendingLineReliability();
			tr.setCoefficients( coefficients ) ;
			tr.setSortCoordinatePoints( pointsMap );
			tr.analyze();
			lineReliabilityMap.put( coefficients , tr);

		}
		
		
		return lineReliabilityMap;
	}
	
	
	private void buildLines(Map<Double ,LineReliability>  valiCoefficients) {
		
		Set<Double> coefficients = valiCoefficients.keySet();
		for (Iterator<Double> coefIter = coefficients.iterator() ; coefIter.hasNext(); ) {
			double key = coefIter.next();
			
			SimpleRelativeEstimateLineEquation srele = new SimpleRelativeEstimateLineEquation();
			srele.setCoefficientA( key);
			srele.setRelativePoint( coordinate.restoreToOriginalDataPoint( coordinate.getOrigin() ) );
			srele.setReliability(  valiCoefficients.get( key ) );
			linesList.add( srele );
		}
		
	}
	

	/**
	 * set basic cluster base group point list
	 * 
	 * clustering all data
	 * 
	 */
	private void mergeToBasicGroupPoints( 
			List<DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint>>
				basicGroupPoints , List<DegreesCoordinatePoint>  commonCoorPoints) {
		
		for (DegreesCoordinatePoint dp : commonCoorPoints) {
			System.out.println(dp);
		}
		
		Map<Integer , Double> maxNeighborInstanceInCycle = new LinkedHashMap<Integer,Double>();
		maxNeighborInstanceInCycle.put(20, 5.5);
		maxNeighborInstanceInCycle.put(100, 0.6);
		
		DegreesCoordinatePoint point = null;
		for (Iterator<DegreesCoordinatePoint>  pointIter =  commonCoorPoints.iterator() ; pointIter.hasNext() ;  ) {
			
			point = pointIter.next();
			double maxNeighborInstance = 0;
			// --- get max instance ---
			for (Map.Entry<Integer, Double> entry : maxNeighborInstanceInCycle.entrySet()) {
				if (point.getXvalue() < entry.getKey() ) {
					maxNeighborInstance = entry.getValue();
					break;
				}
			}

			double lastDistance = 99999999;
			DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint> closeCluster = null;
			
			for (DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint>  clus :  basicGroupPoints) {
				
				double keyInstance = Math.abs( clus.getCenterKeyValue() - point.getDegrees() );
				
				double pointInstance = Math.abs( clus.getCenterPointValue() - point.getDegrees() );
				
				double comparedValue = keyInstance > pointInstance ? keyInstance : pointInstance;
				
				if ( comparedValue < lastDistance) {
					closeCluster = clus;
					lastDistance = comparedValue;
				}
			}			
			
			if ( lastDistance < maxNeighborInstance) {
				
				closeCluster.joinIn( point );
				
			}				
			
		}

		
		
	}
	
	/**
	 * get more line coeffiecient point 
	 * @param basicGroupPoints
	 * @return
	 */
	private List<Double> guessSomeLineCoefficient(List<DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint>> basicGroupPoints , List<DegreesCoordinatePoint>  commonCoorPoints) {
		
		List<Double> guessCoefficient = new Vector<Double>();

		Iterator<DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint>> clusIter = basicGroupPoints.iterator();
		DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint> cluster = null;
		
		EstimatedLineRegression estLineReg = new EstimatedLineRegression();
		while (clusIter.hasNext() ) {
			cluster = clusIter.next();

			estLineReg.setPoints( cluster );

			// --- spent more time
			estLineReg.calculate();
			
			// ---- 
			
			double coefficient = estLineReg.getCoefficient();
			System.out.println("output : " + coefficient);
			// --- validate this coefficient is ok ---
			boolean maybeyes = checkTendencyLine (coefficient , commonCoorPoints );

			if ( maybeyes) {
				guessCoefficient.add( coefficient );
			}
			else {
				clusIter.remove();
			}
			
			if ( logger.isInfoEnabled() ) {
				logger.info("execute program using time : " + (System.currentTimeMillis() - spendStartTime) + "ms");
			}
			
		}
		return guessCoefficient;
	}

	private static DecimalFormat decFormat = new DecimalFormat("0.######");	
	
	private double toDegree(double value) {
		return Double.parseDouble( decFormat.format( Math.toDegrees( Math.atan( value ) ) ) );
	}
	
	
	/**
	 * 1. 两天的收盘价均在趋势性一边,且突破过3%
	 * 2. 有至少有 3个点(x 均为不同) 以上的重合
	 * 
	 * @param coefficient
	 * @param commonCoorPoints
	 * @return
	 */
	private boolean checkTendencyLine(double coefficient , List<DegreesCoordinatePoint>  commonCoorPoints) {
		
		Map<Double, List<DegreesCoordinatePoint>> sortPointsMap = sortPointsMap(commonCoorPoints);
		
		List<Double> pointKeys = new Vector<Double>( sortPointsMap.keySet() ) ;
		boolean breakthrough = false;
		int matchCounter = 0;
		
		DegreesCoordinatePoint coorpoint = null;		
		for (int i = 1; i < pointKeys.size() ; i++) {
			double key = pointKeys.get(i);
			List<DegreesCoordinatePoint> assoPoints = sortPointsMap.get(key);
			
			DegreesCoordinatePoint lowPoint = assoPoints.get(0);
			DegreesCoordinatePoint bottomPoint = assoPoints.get(1);
			double x = lowPoint.getXvalue();
			double yTheory = coefficient * x;
			
			double yInBottom = bottomPoint.getYvalue();
			double yInLow = lowPoint.getYvalue();
			
			// --- match point ---
			double distanceOfBottom = Math.abs( yInBottom - yTheory );
			double distanceOfLow = Math.abs( yInLow - yTheory );
			
			boolean matchPoint = distanceOfLow < 1.5 || distanceOfBottom < 1.5;
			if (matchPoint) {
				matchCounter +=1;
			}
			if (yInBottom < yTheory) {
				
				// --- stop checkint ---
				if ( i == pointKeys.size() - 1) {
					break;
				}
				// --- check the second day ---
				
				double next = pointKeys.get(i+1);
				DegreesCoordinatePoint nextBottomPoint = sortPointsMap.get(next).get(1);
				double nextX = nextBottomPoint.getXvalue();
				double nextY = nextBottomPoint.getYvalue();
				double nextYTheory = coefficient * nextX;
				
				// --- get the next y theory ---
				if ( nextY < nextYTheory) {
					breakthrough = true;
				}
				
			}

			
		}
		
		boolean isTendencyLine = matchCounter > 3 && !breakthrough;
		return isTendencyLine;
	}

	private List<RelativeEstimatedLineEquation> linesList = new Vector<RelativeEstimatedLineEquation>();
	
	@Override
	List<RelativeEstimatedLineEquation> getLines() {
		// TODO Auto-generated method stub
		return linesList;
	}


	/**
	 * gen and sort ponit map 
	 * @param commonCoorPoints
	 * @return
	 */
	private Map<Double, List<DegreesCoordinatePoint>>  sortPointsMap(List<DegreesCoordinatePoint>  commonCoorPoints) {
		Map<Double , List<DegreesCoordinatePoint>>  pointMap = new HashMap<Double, List<DegreesCoordinatePoint>>();
		DegreesCoordinatePoint point = null;
		for (Iterator<DegreesCoordinatePoint>  pointIter = commonCoorPoints.iterator() ; pointIter.hasNext(); ) {
			point = pointIter.next();
			List<DegreesCoordinatePoint> pointList =  pointMap.get( point.getXvalue() );
			if (pointList == null) {
				pointList = new Vector<DegreesCoordinatePoint>();
			}
			pointList.add( point );
			pointMap.put( point.getXvalue() , pointList);
		}
		
		// --- sort by x value ---
		List<Double> kies = new Vector<Double>( pointMap.keySet() );
		Collections.sort( kies );
		
		Map<Double , List<DegreesCoordinatePoint>>  sortPointMap = new LinkedHashMap<Double, List<DegreesCoordinatePoint>>();
		
		for (Iterator<Double>  valuIter = kies.iterator() ; valuIter.hasNext(); ) {
			double value = valuIter.next();
			
			sortPointMap.put( value , pointMap.get( value ));

		}		

		return sortPointMap;
	}
	
	
	private KLineODPFactory klineODPFactory = KLineODPFactory.getInstance();
	
	
	private List<OriginalDataPoint> findCommonPointsinReferedCycle( ) {
		List<OriginalDataPoint> keyPoints = new Vector<OriginalDataPoint>();
		
		OriginalDataPoint odp = null;		
		for (DailyKLineStock dks  : klineInReferedCycle ) {

			
			if (dks.getBottom() == dks.getClose() ) {
				odp = klineODPFactory.createODPFromPriceClose( dks );
			}
			else {
				odp = klineODPFactory.createODPFromPriceOpen(dks);
			}
			keyPoints.add( odp );
		
			// --- add the low point ---
			odp = klineODPFactory.createODPFromPriceLow( dks );
			keyPoints.add( odp );			

			
	
		}
		
		return keyPoints;
		
	}
	
	
	private List<OriginalDataPoint> findKeyDataPointsInReferedCycle() {
		
		List<OriginalDataPoint> keyPoints = new Vector<OriginalDataPoint>();
		
		DefaultZigBuilder zigBuilder = new DefaultZigBuilder();
		zigBuilder.setDailyKLineStockList( klineInReferedCycle );
		List<ZigIndicatorResultItem>    allTroughs = null;
		zigBuilder.setThreshold( (double)4.3 );
		
		try {
			// --- attribute bottom ---
			zigBuilder.setKLineStockAttribute( KLineStock.Attribute.BOTTOM  );
			zigBuilder.buildUp();
			allTroughs =  zigBuilder.getResultItems(  ZigBuilder.ALL_TROUGH);
		
			DailyKLineStock dks = null;
			OriginalDataPoint odp = null;
			for (ZigIndicatorResultItem item : allTroughs ) {
				dks = (DailyKLineStock)item.getAssoRef();

				if (dks.getBottom() == dks.getClose() ) {
					odp = klineODPFactory.createODPFromPriceClose( dks );
				}
				else {
					odp = klineODPFactory.createODPFromPriceOpen(dks);
				}
				keyPoints.add( odp );						
			}

			// --- attribute bottom ---
			zigBuilder.setThreshold( (double)3.0 );
			zigBuilder.setKLineStockAttribute( KLineStock.Attribute.LOW  );
			zigBuilder.buildUp();
			allTroughs =  zigBuilder.getResultItems(  ZigBuilder.ALL_TROUGH);
			
			dks = null;
			odp = null;
			for (ZigIndicatorResultItem item : allTroughs ) {
				dks = (DailyKLineStock)item.getAssoRef();
				odp = klineODPFactory.createODPFromPriceLow( dks );
				keyPoints.add( odp );
			}
		} catch (IndicatorBuilderNotExcutingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return keyPoints;
	}
	
	private Coordinate buildUpCoordinate() {
		CoordinateBuilder builder = new DefaultCoordinateBuilder();
		
		builder.setOriginalDataPoint( odp );
		
		builder.setDailyKLineStockList(this.allImportedListBackground);
		
		builder.buildup();
		// ---get the coordinate --
		Coordinate coordinate = builder.getCoordinate();
		
		return coordinate;
		
	}
	
	/**
	 * set basic cluster base group point list
	 * 
	 * clustering all data
	 * 
	 */
	private List<DegreesCoordinatePointClusterer.Cluster<DegreesCoordinatePoint>> clusterBasicGroupPoints( List<DegreesCoordinatePoint>  allPoints) {
		
		DegreesCoordinatePointClusterer clusterer = new DegreesCoordinatePointClusterer();
		clusterer.setDegreesCoordinatePoints(allPoints);
		
		return clusterer.buildClusterer();
	}

	
	/**
	 * 1. map the coordinate point 
	 * 2. sort coordinate point list 
	 * 
	 * @param keyPoints
	 * @param coordinate
	 * @return
	 */
	private List<DegreesCoordinatePoint>  findCoordinatePointList(List<OriginalDataPoint> keyPoints ) {
		
		OriginalDataPoint oriDataPoint = null;
		CoordinatePoint cp = null; 
		
		Vector<Sortable> dcpVec = new Vector<Sortable>();
		for (int i = 0  ; i < keyPoints.size() ; i++) {
			oriDataPoint = keyPoints.get(i);
			
			cp = coordinate.convertToCoordinatePoint( oriDataPoint );

			DegreesCoordinatePoint dcp = new DegreesCoordinatePoint();
			dcp.setXvalue( cp.getXvalue() );
			dcp.setYvalue( cp.getYvalue() );

			dcpVec.add( dcp );
		}

		// --- sort by list ---
		ListQuickSort lqs = ListQuickSort.getInstance();
		lqs.setSortList( dcpVec );
		lqs.calculate();

		List<DegreesCoordinatePoint> output = new Vector<DegreesCoordinatePoint>();
		for (Iterator<Sortable>  iterB = dcpVec.iterator() ; iterB.hasNext(); ) {
			Sortable b2sed = iterB.next();
			output.add( (DegreesCoordinatePoint) b2sed );
		}


		return output;
	}
	
	private List<DailyKLineStock> allImportedListBackground;

	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingLineBuilder#setDailyKLineStockList(java.util.List)
	 */
	@Override
	void setDailyKLineStockList(List<DailyKLineStock> importedList) {
		// TODO Auto-generated method stub
		allImportedListBackground = importedList;
	}

	
	private List<DailyKLineStock> klineInReferedCycle;
	
	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingLineBuilder#setKlineStockInReferedCycle(java.util.List)
	 */
	@Override
	void setKlineStockInReferedCycle(List<DailyKLineStock> importedList) {
		// TODO Auto-generated method stub
		klineInReferedCycle = importedList;
	}

	private OriginalDataPoint odp;

	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.TrendingLineBuilder#setOriginalDataPoint(org.vsg.stock.core.indicator.coordinate.OriginalDataPoint)
	 */
	@Override
	void setOriginalDataPoint(OriginalDataPoint odp) {
		// TODO Auto-generated method stub
		this.odp = odp;
	}


}
